package com.oneinlet

import java.io.Serializable
import java.util.*
import java.util.concurrent.ConcurrentHashMap


/**
 * Created by WangZiHe on 2018/5/13
 * QQ/WeChat:648830605
 * QQ-Group:368512253
 * Blog:www.520code.net
 * Github:https://github.com/yancheng199287
 */

/**
 * 一个超级简单且使用的缓存帮手，如果你只是想简单缓存下，无需导入其他重量框架
 * 该缓存框架不保证严格的精确过期策略，请勿以此为准。
 */
object YCache {

    /**
     * 创建一个缓存对象，传入过期时间，如果为null或小于0则是不过时
     * 注意：该缓存类实现了一个非精确定时过期策略的缓存，如果不用到过期或者想采取惰性（在取的时候判断过期），大可自己直接使用map实现缓存
     * 如果需要使用非常精确的时间也不要使用！
     * 实际开发中，对过期时间策略的要求很高的不多，如果有这样的需求请采取知名开源框架
     * @param maxTime 最长存活时间 默认是30分钟，单位是秒, 注意，该值，如果你不确定，可以不填，如果填写，请尽量至少大于一分钟，否则没有意义
     * @param maxCount 最多阀值，默认是1000个
     * @param expireEvent 过期事件回调对象，如果该key过期会回调此事件，开发人员在这里实现自己的过期业务
     */
    fun newCache(maxTime: Int = 30 * 60 * 60, maxCount: Int = 1000, expireEvent: ExpireEvent? = null): SeaCache {
        return SeaCache(maxTime, maxCount, expireEvent)
    }


    class SeaCache(private val maxTime: Int, private val maxCount: Int, expireEvent: ExpireEvent? = null) : ConcurrentHashMap<String, Couple>() {

        init {
            if (expireEvent != null) {
                val checkExpirePolicy = CheckExpirePolicy(this, expireEvent)
                checkExpirePolicy.start()
            }
        }

        fun putKeyValue(key: String, value: Any, keyMaxTime: Int? = null) {
            if (maxCount <= size) {
                throw RuntimeException("添加缓存时已经超过最大数量阀值，最大阀值是：$maxCount, 当前容量是：$size")
            }
            val now = Date()
            val deadTime = if (keyMaxTime == null) {
                YCDate.addDate(now, maxTime, YCDate.DateUnit.SECOND)
            } else {
                YCDate.addDate(now, keyMaxTime, YCDate.DateUnit.SECOND)
            }
            val couple = Couple(key, value, now, deadTime)
            put(key, couple)
        }


        fun <T> getValue(key: String): T? {
            val couple = get(key) ?: return null
            return couple.value as T
        }
    }

    data class Couple(
            var key: String,
            var value: Any,
            private var bornTime: Date,
            private var deadTime: Date
    ) : Serializable {
        fun isDead(): Boolean {
            return !YCDate.dateAtBetween(Date(), bornTime, deadTime)
        }
    }

    interface ExpireEvent {
        fun handleEvent(key: String, value: Any)
    }

    class CheckExpirePolicy(private val seaCache: SeaCache, private val expireEvent: ExpireEvent? = null) : Thread() {
        override fun run() {
            while (true) {
                if (YCollections.isNotEmpty(seaCache)) {
                    for ((key, value) in seaCache) {
                        if (value.isDead()) {
                            seaCache.remove(key)
                            expireEvent!!.handleEvent(key, value.value)
                        }
                    }
                }
                Thread.sleep(10000)
            }

        }
    }


}